package com.uw.tookit;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;

import com.uw.tookit.MyLocation.LocationResult;

import android.app.Activity;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class Results extends Activity {
	private final String paramCategory = "category";
	private final String paramSearchBox = "q";
	private final String resultNameKey = "resultName";
	private final String resultDistanceKey = "resultDistance";
	private final double radiusEarth = 106.105;
	
    private boolean buttonShowAll = false; //the button text type
    private ArrayList<ResultPageData> dataHolder = new ArrayList<ResultPageData>(); //holds the search results
    private ArrayList<Map<String, String>> sortedResults = new ArrayList<Map<String, String>>();
    private ArrayList<Map<String, String>> listResults = new ArrayList<Map<String, String>>();
    private ResultListViewAdaptor a; // adaptor for listview.
    private boolean mapButtonEnabled = false;
    private boolean gps_enabled = false;
    private boolean network_enabled = false;
    
	/**
	 * This Activity loads the results page of our application. A list of search results is 
	 * displayed on this page.
	 */
	
	/** Called when the activity is first created. */
	public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.results);
    	
    	ArrayList<Map<String, String>> searchItems = getSearchResultsToDisplay();
    	Log.i("CategoryItems Length: ", "" + searchItems.size());
    	
    	// Add buttons, then the list
    	ListView list = (ListView) findViewById(R.id.listView);
    	Log.i("Reached: ", "" + list.toString());
    	copyArray(listResults, sortedResults);
    	//a = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listResults);
    	a = new ResultListViewAdaptor(this, listResults);
    	list.setAdapter(a);
    	
    	// add event listeners
    	addListenerOnListOptions();
    	addListenerOnFilterButton();
    	addListenerOnMapButton();
    }
	
	/**
	 * Adds event listener to the each result in the list. When the result is clicked
	 * by user, the business ID of the page is retrieved, and the user is transferred to the
	 * information page
	 */
	private void addListenerOnListOptions() {
		ListView list = (ListView) findViewById(R.id.listView);
		list.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View view, int position,	long id) {
				String itemClicked = a.getItemResultName(position);
				int business_id = -1;
				// find the business ID associated with this clicked result
				for(int i = 0; i < dataHolder.size(); i++) {
					if(dataHolder.get(i).getResultName().equalsIgnoreCase(itemClicked))
						business_id = dataHolder.get(i).getBusinessId();
				}
				// transfer to information activity
				if (business_id != -1) {
					Intent info = new Intent(Results.this, InformationActivity.class);
					info.putExtra("businessID", business_id);
					startActivity(info);
				}
			}
      	});
	}
	
	/**
	 * @param listResults: the array to which data is copied
	 * @param a: the array from which the data is copied
	 */
	private void copyArray(ArrayList<Map<String, String>> listResults, ArrayList<Map<String, String>> a) {
		listResults.clear();
		for (int i = 0; i < a.size(); i++) {
			listResults.add(a.get(i));
		}
	}
	
	/**
	 * Adds an event listener to the openNow button. Function determines whether the button is
	 * clicked for "View All" or "View open Now", and shows the correct results accordingly.
	 */
	public void addListenerOnFilterButton() {
    	final Button button = (Button) findViewById(R.id.buttonFilter);
    	button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				// User clicked on "View All"
				if (buttonShowAll) {
					// change the text of the button
					button.setText(R.string.buttonOpenOnlyText);
					buttonShowAll = false;
					// show all results
					copyArray(listResults, sortedResults);
					a.notifyDataSetChanged();
				// User clicked on "View Open Only"
				} else {
					// change the text of the button
					button.setText(R.string.buttonAllText);
					buttonShowAll = true;
					// show open only results
					for (int i = 0; i < dataHolder.size(); i++) {
						ResultPageData r = dataHolder.get(i);
						if (!r.isOpenNow()) {
							removeFromListResults(r.getResultName());
						}
					}
					a.notifyDataSetChanged();
				}
			}
    	});
	}
	
	/**
	 * @param resultName: the name of the ResultPageData which is to be removed
	 */
	private void removeFromListResults(String resultName) {
		for (int i = 0; i < listResults.size(); i++) {
			Map<String, String> item = listResults.get(i);
			if (item.get(this.resultNameKey).equalsIgnoreCase(resultName)) {
				listResults.remove(i);
				return;
			}
		}
	}
	
	/**
	 * Adds an event listener on the Map button. When user clicks on this button,
	 * the user is redirected to the CampusMap activity. All the result data retrieved 
	 * from server is passed on to CampusMap Activity.
	 */
	public void addListenerOnMapButton() {
    	final Button button = (Button) findViewById(R.id.buttonMap);
    	button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				if(mapButtonEnabled) {
					Intent resultsPage = new Intent(Results.this, CampusMap.class);
		            resultsPage.putParcelableArrayListExtra("results", dataHolder);
		            startActivityForResult(resultsPage, 0);
				} else {
					return;
				}			
			}
    	});
	}
	
	/**
	 * @return: a list of sorted search results which are to be displayed
	 */
	private ArrayList<Map<String, String>> getSearchResultsToDisplay() {
		// Makes a query to the server with the correct parameters.
		Map<String, String> paramsMapKeys = getParametersToQuery();
    	String results = ServerCommunicator.executeHttpRequest("search.php", paramsMapKeys);
    	// If server connection successful
    	if (results != null) {
	    	// Prints the results if returned. If empty string returned, e;se prints "No Results Found"
	    	if (results.length() != 0) {
		    	String[] searchResults = results.split(",");
		    	// Skip the 0th index, as that is empty.
		  		for(int i=1; i< searchResults.length - 1;i++) {
					String[] categoryInfo = searchResults[i].split("~");
					// If i-th search result is not empty. Then store the results in the dataholder
					if (categoryInfo[0].trim().length() != 0) {
						String resultName = categoryInfo[0].trim();
						int business_id = Integer.parseInt(categoryInfo[1]);
						double latitude = Double.parseDouble(categoryInfo[2]);
						double longitude = Double.parseDouble(categoryInfo[3]);
						boolean open = categoryInfo[4].equalsIgnoreCase("true") ? true : false;
						dataHolder.add(new ResultPageData(resultName, business_id, latitude, longitude, open));
					}
		  		}
		  		// Sort the results in terms of distance
		  		sortedResults = sortResultsInTermsOfDistance(dataHolder);
		  		mapButtonEnabled = true;
		  	// results.length == 0 -> no results found in the server
	    	} else {
	    		if (paramsMapKeys.containsKey(paramCategory)) {
	    			Map<String, String> m = new HashMap<String, String>();
	    			m.put(resultNameKey, "No results found for category \"" + paramsMapKeys.get(paramCategory) + "\"");
	    			m.put(resultDistanceKey, "");
	    			sortedResults.add(m);
	    		} else if (paramsMapKeys.containsKey(paramSearchBox)) {
	    			Map<String, String> m = new HashMap<String, String>();
	    			m.put(resultNameKey, "No results found for \"" + paramsMapKeys.get(paramSearchBox) + "\"");
	    			m.put(resultDistanceKey, "");
	    			sortedResults.add(m);
	    		} else {
	    			Map<String, String> m = new HashMap<String, String>();
	    			m.put(resultNameKey, "No results found" + paramsMapKeys.get(paramCategory) + "\"");
	    			m.put(resultDistanceKey, "");
	    			sortedResults.add(m);
	    		}
	    		mapButtonEnabled = false;
	    	}
	    // If results == null, then no internet connection found error.
    	} else {
    		Map<String, String> m = new HashMap<String, String>();
    		m.put(resultNameKey, "Please connect to the internet.");
			m.put(resultDistanceKey, "");
			sortedResults.add(m);
    	}
		return sortedResults;
	}
	
	/**
	 * @return: the current location of the user. Null is no provider.
	 */
	private Location getLocation() {
	    LocationManager lm = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
	    Location net_loc=null, gps_loc=null;
	    try{gps_enabled=lm.isProviderEnabled(LocationManager.GPS_PROVIDER);}catch(Exception ex){}
        try{network_enabled=lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);}catch(Exception ex){}
        if(gps_enabled)
            gps_loc=lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        if(network_enabled)
            net_loc=lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        if (gps_loc != null) {
        	return gps_loc;
        } else {
        	return net_loc;
        }
	}
	
	/**
	 * Calculates the distance in miles between current latitude/longitude to all the results.
	 * Then sorts the results by distance and returns it.
	 */
	private ArrayList<Map<String, String>> sortResultsInTermsOfDistance(ArrayList<ResultPageData> dataHolder) {
		Location location = getLocation();
		double latitude;
		double longitude;
		// current latitude and longtitude
		if (location != null) {
			latitude = location.getLatitude();
			longitude = location.getLongitude();
		} else {
			// if GPS location not found, don't sort, just return the results
			return dataHolderToListResultItems(false);
		}
		// calculate the distance in miles 
		for (int i = 0; i < dataHolder.size(); i++) {
			ResultPageData result = dataHolder.get(i);
			//cos(AOB)=cos(latA)cos(latB)cos(lonB-lonA)+sin(latA)sin(latB);
			double cosAngle = (Math.cos(latitude) * Math.cos(result.getLatitude()) * Math.cos(result.getLongitude() - longitude)) 
						 + (Math.sin(latitude) * Math.sin(result.getLatitude()));
			double actualHorizontalDistance = Math.acos(cosAngle) * radiusEarth;
			result.setDistanceFromCurrent(actualHorizontalDistance);
		}
		return getSortedResults();
	}
	
	/**
	 * @return: performs the actual sorting.
	 */
	private ArrayList<Map<String, String>> getSortedResults() {
		Collections.sort(dataHolder, new Comparator<ResultPageData>() {
			public int compare(ResultPageData arg0, ResultPageData arg1) {
				if (arg0.getDistanceFromCurrent() < arg1.getDistanceFromCurrent()) {
					return -1;
				} else if (arg0.getDistanceFromCurrent() > arg1.getDistanceFromCurrent()) {
					return 1;
				} 
				return 0;
			}
			
		});
		// Put the results in the right format requires to display the data.
		return dataHolderToListResultItems(true);
	}

	/**
	 * @param distanceSet: whether the distance information is to be displayed in results page or not
	 * @return the items to be displayed in the results page. Each item has a String name, and String distance
	 *        if !distanceSet, then String distance is empty
	 */
	private ArrayList<Map<String, String>> dataHolderToListResultItems(boolean distanceSet) {
		ArrayList<Map<String, String>> searchItems = new ArrayList<Map<String, String>>();
		for (int i = 0; i < dataHolder.size(); i++) {
			Map<String, String> m = new HashMap<String, String>();
			m.put(resultNameKey, dataHolder.get(i).getResultName());
			String distance = (distanceSet) 
					? roundTwoDecimals(dataHolder.get(i).getDistanceFromCurrent()) + "mi" : "";
			m.put(resultDistanceKey, distance);
			searchItems.add(m);
		}
		return searchItems;
	}
	
	/**
	 * @param d: the double to round up
	 * @return: the double rounded up to two decimal places
	 */
	private double roundTwoDecimals(double d) {
        DecimalFormat twoDForm = new DecimalFormat("#.##");
        return Double.valueOf(twoDForm.format(d));
	}

	/**
	 * Gets the parameters passed in to Results. The parameters passed in is the key-value
	 * query parameters with which the server needs to be queried.
	 * These parameters are passed in using two string ArrayLists - one for the parameter key names
	 * and the other for the values of those keys. Index 0 of 'paramNames' corresponds to index 0 of
	 * 'paramValues'.
	 * @return a Map with the proper key-value representation.
	 */
	private Map<String, String> getParametersToQuery() {
		Bundle extras = getIntent().getExtras();
    	ArrayList<String> paramNames = new ArrayList<String>();
    	ArrayList<String> paramValues = new ArrayList<String>();
    	if(extras !=null) {
    		paramNames = extras.getStringArrayList("paramNames");
    		paramValues =  extras.getStringArrayList("paramValues");
    	}
    	Map<String, String> paramsMapKeys = new HashMap<String, String>();
    	if (paramValues.size() == paramNames.size() && paramValues.size() != 0) {
    		for (int i = 0; i < paramNames.size(); i++) {
    			paramsMapKeys.put(paramNames.get(i), paramValues.get(i));
    		}
    	} else {
    		paramsMapKeys = null;
    	}
		return paramsMapKeys;
	}
}